package com.yc.spring.aop;

import com.yc.spring.IocConfig;
import com.yc.spring.MyAnnotationConfigApplicationContext;
import com.yc.spring.bbs.dao.UserDao;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;


import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AopApplicationContext extends MyAnnotationConfigApplicationContext {


    //切面对象集合
    Map<String,Object> aspectMap = new HashMap<>();
    //切点 =》增强方法
    Map<String, List<Method>> pointcutMap = new HashMap<>();
    //需要动态代理的对象集合
    Map<String,Object> proxyBeanMap = new HashMap<>();

    public AopApplicationContext(Class configClass) throws NoSuchMethodException {
        //思考题：为什么编译器强制要求我们定义该构造函数
        super(configClass);


        //2查找组件中的切面类 =》@Aspect 注解
        this.singletonObjects.forEach((id,bean)->{
            //判断切面类
            if(bean.getClass().getAnnotation(Aspect.class) != null){
                aspectMap.put(id,bean);
            }
        });

        //3.统计切点与增强 Pointcut + Advice, 封装切面信息对象, 保存切面集合中
        for (Object aspectBean : aspectMap.values()) {
            for (Method method : aspectBean.getClass().getDeclaredMethods()) {
                //只实现前置增强
                Before before = method.getAnnotation(Before.class);
                if ( before!=null) {
                    //提取切点表达式
                    String value = before.value();
                    String pointcut = null;
                    if(value.matches("\\w+\\(\\)")){
                        //@After("myPointcut()")
                        String $1 = value.replaceAll("(\\w)\\(\\)", "$1");
                        //获取切点方法
                        Method pointcutMethod = aspectBean.getClass().getDeclaredMethod($1);
                        //获取切点方法上的切点
                        Pointcut pointcutAnno = pointcutMethod.getAnnotation(Pointcut.class);
                        pointcut = pointcutAnno.value();
                    }
                    else{
                        //@Before("execution(* com.yc.spring.bbs.dao.UserDaoImpl.*(..))")
                        pointcut = value;
                    }
                    //将切点  和 增强方法  添加到集合中
                    List<Method> list = pointcutMap.get(pointcut);
                    //如果list 为空  那么是第一次添加
                    if(list==null){
                        list = new ArrayList<>();
                        pointcutMap.put(pointcut,list);
                    }
                    list.add(method);


                }


            }

        }


        //4.扫描所有的组件, 查看是否包含被切点拦截的方法
        this.singletonObjects.forEach((id,bean)->{
            for (Method method : bean.getClass().getDeclaredMethods()) {
               a: for (String pointcut : pointcutMap.keySet()) {
                    if (isPointcut(pointcut,method)) {
                        proxyBeanMap.put(id,bean);
                        //只要有一个方法被切到  那么就要代理  即可终止循环判断
                        break  a;
                    }
                }
            }

        });


        //如果组件包含被切点拦截的方法 那么生成动态代理对象替换原有的组件
        proxyBeanMap.forEach((id,bean)->{
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if(interfaces ==null || interfaces.length ==0){
                //cglib 动态代理
            }
            else {
                //jdk代理
                jdkProxy(id,bean);
            }
        });


    }


    private void jdkProxy(String id,Object bean){
        //动态生产代理对象
        Object proxyBean = Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy,method,args)->{
                    //jdk stream 编程
                    try{
                        // 前置增强
                        pointcutMap
                                // 双列集合转单列集合  Set<Entry<pointcut, list<method>>>
                                .entrySet()
                                // 集合转成 流
                                .stream()
                                // 过滤切点
                                .filter(entry->isPointcut(entry.getKey(),method))
                                // 将 map里面的list的元素提取出来
                                .flatMap(entry->entry.getValue().stream())
                                // 过滤出前置拦截方法集合
                                .filter(adviceMethod->adviceMethod.getAnnotation(Before.class)!=null)
                                // 执行所有的前置增强方法
                                .forEach(beforeMethod->{
                                    try {
                                        beforeMethod.invoke(bean);
                                    } catch (Exception e) {
                                        throw new RuntimeException("前置增强方法执行失败!",e);
                                    }
                                });
                        Object ret = method.invoke(bean, args);
                        // 返回增强
                        return ret;
                    }
                    catch (Throwable t){
                        //异常增强
                        throw t;
                    }
                    finally {
                        //后置增强
                    }

                });
        //替换原有的 bean
        singletonObjects.put(id,proxyBean);
    }



    //判断该方法是否被切到
    public boolean isPointcut(String pointcut,Method method){
        //注意：只处理  execution 表达式
        //去掉execution()
        String $1 = pointcut.replaceAll("execution\\((.+)\\)", "$1");
        //转换为正则表达式
        pointcut = pointcut.replaceAll("\\*|\\.\\.",".*");
        pointcut = pointcut.replaceAll("\\(","\\\\(");
        pointcut = pointcut.replaceAll("\\)","\\\\)");
      //  System.out.println("******************pointcut = " + pointcut);

        String methodSring = method.toString();
        //判断是否被切到
        return methodSring.matches(pointcut);


    }


    public static void main(String[] args) throws NoSuchMethodException {
        AopApplicationContext cxt = new AopApplicationContext(IocConfig.class);
        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
        cxt.pointcutMap.forEach((pointcut,list)->{
            System.out.println(pointcut);
            System.out.println(list);
        });
        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
        cxt.proxyBeanMap.forEach((id,bean)->{
            System.out.println(id);
            System.out.println(bean);
        });

        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
        UserDao userDaoImpl =(UserDao) cxt.getBean("userDaoImpl");
        userDaoImpl.insert(null);


    }

}
